/* * JBoss, a division of Red Hat * Copyright 2006, Red Hat Middleware, LLC, and individual contributors as indicated * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.picketlink.idm.common.transaction; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.NotSupportedException; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.RollbackException; import org.apache.log4j.Logger; /** * Utility class for managing transactions. * * @author <a href="mailto:julien@jboss.org">Julien Viet</a> * @version $Revision: 5451 $ */ public class Transactions { /** . */ private static Logger log = Logger.getLogger(Transactions.class); /** . */ private static final String[] STATUS_NAMES = { "STATUS_ACTIVE", "STATUS_MARKED_ROLLBACK", "STATUS_PREPARED", "STATUS_COMMITTED", "STATUS_ROLLEDBACK", "STATUS_UNKNOWN", "STATUS_NO_TRANSACTION", "STATUS_PREPARING", "STATUS_COMMITTING", "STATUS_ROLLING_BACK"}; /** * Decode the status name. * * @param status the status value * @return the translated status name or null if it is not valid */ public static String decodeStatus(int status) { if (status >= 0 && status <= STATUS_NAMES.length) { return STATUS_NAMES[status]; } else { return null; } } /** * Apply the transaction type before the unit of work. * * @param type the transaction type * @param tm the transaction manager * @return the new transaction if one has been started. * @throws TransactionException * @throws IllegalArgumentException if the type or the transaction manager is null */ public static Transaction applyBefore(Type type, TransactionManager tm) throws TransactionException, IllegalArgumentException { if (tm == null) { throw new IllegalArgumentException("No transaction manager provided"); } if (type == null) { throw new IllegalArgumentException("No type"); } // Suspend the incoming transaction Transaction oldTx = suspend(tm); if (oldTx != null) { type.txBefore(tm, oldTx); } else { type.noTxBefore(tm); } return oldTx; } /** * Apply the transaction type after the unit of work has been done. * * @param type the transaction type * @param tm the transaction manager * @param oldTx the old transaction if it is not null * @throws TransactionException * @throws IllegalArgumentException if the type of the transaction manager is null */ public static void applyAfter(Type type, TransactionManager tm, Transaction oldTx) throws TransactionException, IllegalArgumentException { if (tm == null) { throw new IllegalArgumentException("No transaction manager provided"); } if (type == null) { throw new IllegalArgumentException("No type"); } try { if (oldTx != null) { type.txAfter(tm, oldTx); } else { type.noTxAfter(tm); } } finally { if (oldTx != null) { try { resume(tm, oldTx); } catch (TransactionException ignore) { log.error("Was not capable to resume the incoming transaction", ignore); } } } } /** * Apply the transaction type around the unit of work. * * @param type the transaction type * @param tm the transaction manager * @param runnable the unit of work * @return the object returned by the runnable object * @throws NestedException wraps any exception throws by the runnable object * @throws TransactionException * @throws IllegalArgumentException if any method argument is null */ public static Object apply(Type type, TransactionManager tm, final Runnable runnable) throws NestedException, TransactionException, IllegalArgumentException { if (tm == null) { throw new IllegalArgumentException("No transaction manager provided"); } if (runnable == null) { throw new IllegalArgumentException("No code to execute"); } if (type == null) { throw new IllegalArgumentException("No type"); } // Any throwable thrown by the wrapped code Throwable throwable = null; // Any object returned by the wrapped code Object ret = null; // Suspend the incoming transaction Transaction oldTx = suspend(tm); try { if (oldTx != null) { type.txBefore(tm, oldTx); try { ret = runnable.run(); } catch (Throwable t) { throwable = t; } finally { type.txAfter(tm, oldTx); } } else { type.noTxBefore(tm); try { ret = runnable.run(); } catch (Throwable t) { throwable = t; } finally { type.noTxAfter(tm); } } } finally { if (oldTx != null) { try { resume(tm, oldTx); } catch (TransactionException ignore) { log.error("Was not capable to resume the incoming transaction", ignore); } } } if (throwable != null) { if (throwable instanceof Error) { throw (Error)throwable; } else { throw new NestedException(throwable); } } else { return ret; } } public static Object notSupported(TransactionManager tm, Runnable runnable) throws NestedException, TransactionException { return apply(TYPE_NOT_SUPPORTED, tm, runnable); } public static Object never(TransactionManager tm, Runnable runnable) throws NestedException, TransactionException { return apply(TYPE_NEVER, tm, runnable); } public static Object mandatory(TransactionManager tm, Runnable runnable) throws NestedException, TransactionException { return apply(TYPE_MANDATORY, tm, runnable); } public static Object supports(TransactionManager tm, Runnable runnable) throws NestedException, TransactionException { return apply(TYPE_SUPPORTS, tm, runnable); } public static Object required(TransactionManager tm, Runnable runnable) throws NestedException, TransactionException { return apply(TYPE_REQUIRED, tm, runnable); } public static Object requiresNew(TransactionManager tm, Runnable runnable) throws NestedException, TransactionException { return apply(TYPE_REQUIRES_NEW, tm, runnable); } /** * Begin a new transaction. * * @param tm the transaction manager * @throws IllegalArgumentException if the tm is null */ public static void begin(TransactionManager tm) throws IllegalArgumentException, TransactionException { try { if (tm == null) { throw new IllegalArgumentException("No transaction manager"); } tm.begin(); } catch (SystemException e) { log.error("Problem when beginning transaction", e); throw new TransactionException(e); } catch (NotSupportedException e) { log.error("Problem when beginning transaction", e); throw new TransactionException(e); } } /** * Mark the transaction as rollback only. * * @param tx the transaction to mark as rollback only * @throws IllegalArgumentException if the transaction is null * @throws TransactionException */ private static void setRollbackOnly(Transaction tx) throws IllegalArgumentException, TransactionException { try { if (tx == null) { throw new IllegalArgumentException("No transaction to set rollback only"); } tx.setRollbackOnly(); } catch (SystemException e) { log.error("Problem when setting transaction as rollback only", e); throw new TransactionException(e); } } /** * Mark the active transaction for this thread as rollback only * * @see #setRollbackOnly(javax.transaction.Transaction) * @param tm the transaction manager * @throws IllegalArgumentException if the tm is null */ public static void setRollbackOnly(TransactionManager tm) throws IllegalArgumentException, TransactionException { try { if (tm == null) { throw new IllegalArgumentException("No transaction manager"); } Transaction tx = tm.getTransaction(); if (tx == null) { throw new TransactionException("No active transaction to set rollback only"); } setRollbackOnly(tx); } catch (SystemException e) { log.error("Problem when setting transaction as rollback only", e); throw new TransactionException(e); } } public void safeSetRollbackOnly(TransactionManager tm) { try { setRollbackOnly(tm); } catch (IllegalArgumentException e) { log.error("", e); } catch (TransactionException e) { log.error("", e); } } public static void safeEnd(TransactionManager tm) { try { end(tm); } catch (IllegalArgumentException e) { log.error("", e); } catch (TransactionException e) { log.error("", e); } } /** * Terminate the active transaction for this thread. If the transaction is marked for rollback * then it is rollbacked otherwise it is commited. * * @param tm the transaction manager * @return true if commit happened, false otherwise * @throws IllegalArgumentException if the tm is null */ public static boolean end(TransactionManager tm) throws IllegalArgumentException, TransactionException { try { if (tm == null) { throw new IllegalArgumentException("No transaction manager"); } int status = tm.getStatus(); switch (status) { case Status.STATUS_MARKED_ROLLBACK: tm.rollback(); return false; case Status.STATUS_ACTIVE: tm.commit(); return true; default: throw new TransactionException("Abnormal status for ending a tx " + STATUS_NAMES[status]); } } catch (SystemException e) { log.error("Problem when ending transaction", e); throw new TransactionException(e); } catch (HeuristicMixedException e) { log.error("Problem when ending transaction", e); throw new TransactionException(e); } catch (HeuristicRollbackException e) { log.error("Problem when ending transaction", e); throw new TransactionException(e); } catch (RollbackException e) { log.error("Problem when ending transaction", e); throw new TransactionException(e); } } /** * Associate the thread with a transaction * * @param tm the transaction manager * @param tx the transaction to associate with the this thread * @throws IllegalArgumentException if any argument is null * @throws TransactionException */ public static void resume(TransactionManager tm, Transaction tx) throws IllegalArgumentException, TransactionException { try { if (tm == null) { throw new IllegalArgumentException("No transaction manager"); } if (tx == null) { throw new IllegalArgumentException("No transaction to resume"); } tm.resume(tx); } catch (Exception e) { log.error("Problem when resuming transaction", e); throw new TransactionException(e); } } /** * Disassociate the current thread with the active transaction. * * @param tm the transaction manager * @return the transaction previously associated with this thread * @throws IllegalArgumentException if the transaction manager is null * @throws TransactionException */ public static Transaction suspend(TransactionManager tm) throws IllegalArgumentException, TransactionException { try { if (tm == null) { throw new IllegalArgumentException("No transaction manager"); } return tm.suspend(); } catch (SystemException e) { log.error("Problem when suspending transaction", e); throw new TransactionException(e); } } public interface Runnable { Object run() throws Exception; } public abstract static class Type { private final String name; private Type(String name) { this.name = name; } public Transaction before(TransactionManager tm) { return applyBefore(this, tm); } public void after(TransactionManager tm, Transaction oldTx) { applyAfter(this, tm, oldTx); } abstract void txBefore(TransactionManager tm, Transaction oldTx) throws TransactionException; abstract void txAfter(TransactionManager tm, Transaction oldTx); abstract void noTxBefore(TransactionManager tm) throws TransactionException; abstract void noTxAfter(TransactionManager tm); public String getName() { return name; } public String toString() { return name; } } public static final Type TYPE_NOT_SUPPORTED = new Type("NOT_SUPPORTED") { void txBefore(TransactionManager tm, Transaction oldTx) { } void txAfter(TransactionManager tm, Transaction oldTx) { } void noTxBefore(TransactionManager tm) { } void noTxAfter(TransactionManager tm) { } }; public static final Type TYPE_SUPPORTS = new Type("SUPPORTS") { void txBefore(TransactionManager tm, Transaction oldTx) throws TransactionException { resume(tm, oldTx); } void txAfter(TransactionManager tm, Transaction oldTx) { try { suspend(tm); } catch (IllegalStateException ignore) { log.error("Problem when suspending transaction", ignore); } } void noTxBefore(TransactionManager tm) { } void noTxAfter(TransactionManager tm) { } }; public static final Type TYPE_REQUIRED = new Type("REQUIRED") { void txBefore(TransactionManager tm, Transaction oldTx) { resume(tm, oldTx); } void txAfter(TransactionManager tm, Transaction oldTx) { try { suspend(tm); } catch (TransactionException ignore) { log.error("Problem when suspending transaction", ignore); } } void noTxBefore(TransactionManager tm) throws TransactionException { begin(tm); } void noTxAfter(TransactionManager tm) { try { end(tm); } catch (IllegalStateException ignore) { log.error("Problem when ending transaction", ignore); } } }; public static final Type TYPE_REQUIRES_NEW = new Type("REQUIRES_NEW") { void txBefore(TransactionManager tm, Transaction oldTx) throws TransactionException { begin(tm); } void txAfter(TransactionManager tm, Transaction oldTx) { try { end(tm); } catch (IllegalStateException ignore) { log.error("Problem when ending transaction", ignore); } } void noTxBefore(TransactionManager tm) throws TransactionException { begin(tm); } void noTxAfter(TransactionManager tm) { try { end(tm); } catch (IllegalStateException ignore) { log.error("Problem when ending transaction", ignore); } } }; public static final Type TYPE_MANDATORY = new Type("MANDATORY") { void txBefore(TransactionManager tm, Transaction oldTx) throws TransactionException { resume(tm, oldTx); } void txAfter(TransactionManager tm, Transaction oldTx) { } void noTxBefore(TransactionManager tm) throws TransactionException { throw new TransactionException("No incoming transaction"); } void noTxAfter(TransactionManager tm) { throw new UnsupportedOperationException("Should never ne called"); } }; public static final Type TYPE_NEVER = new Type("NEVER") { void txBefore(TransactionManager tm, Transaction oldTx) throws TransactionException { throw new TransactionException("Need no incoming transaction"); } void txAfter(TransactionManager tm, Transaction oldTx) { throw new UnsupportedOperationException("Should never ne called"); } void noTxBefore(TransactionManager tm) { } void noTxAfter(TransactionManager tm) { } }; }